package cc.shacocloud.mirage.utils;

import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;


/**
 * 这个类的目的是为了能够捕获和传递一个通用的{@link Type}。
 * 为了捕获通用类型并在运行时保留它，你需要创建一个子类（最好是匿名的内联类），如下所示。
 * <pre class="code">
 * ParameterizedTypeReference<List<String>> typeRef = new ParameterizedTypeReference<List<String>>() {};
 * </pre>
 */
public abstract class ParameterizedTypeReference<T> {
    
    private final Type type;
    
    
    protected ParameterizedTypeReference() {
        Class<?> parameterizedTypeReferenceSubclass = findParameterizedTypeReferenceSubclass(getClass());
        Type type = parameterizedTypeReferenceSubclass.getGenericSuperclass();
        if (!(type instanceof ParameterizedType)) {
            throw new IllegalArgumentException("类型必须是一个参数化的类型");
        }
        
        ParameterizedType parameterizedType = (ParameterizedType) type;
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        if (actualTypeArguments.length != 1) {
            throw new IllegalArgumentException("类型参数的数量必须是1");
        }
        
        this.type = actualTypeArguments[0];
    }
    
    private ParameterizedTypeReference(Type type) {
        this.type = type;
    }
    
    /**
     * 建立一个包裹给定类型的{@code ParameterizedTypeReference}
     *
     * @param type 一个通用类型
     */
    @Contract(value = "_ -> new", pure = true)
    public static <T> @NotNull ParameterizedTypeReference<T> forType(Type type) {
        return new ParameterizedTypeReference<T>(type) {
        };
    }
    
    private static @NotNull Class<?> findParameterizedTypeReferenceSubclass(@NotNull Class<?> child) {
        Class<?> parent = child.getSuperclass();
        if (Object.class == parent) {
            throw new IllegalStateException("超出预期的 ParameterizedTypeReference 超类！");
        } else if (ParameterizedTypeReference.class == parent) {
            return child;
        } else {
            return findParameterizedTypeReferenceSubclass(parent);
        }
    }
    
    public Type getType() {
        return this.type;
    }
    
    @Override
    public boolean equals(@Nullable Object other) {
        return (this == other || (other instanceof ParameterizedTypeReference &&
                this.type.equals(((ParameterizedTypeReference<?>) other).type)));
    }
    
    @Override
    public int hashCode() {
        return this.type.hashCode();
    }
    
    @Override
    public String toString() {
        return "ParameterizedTypeReference<" + this.type + ">";
    }
    
}
